home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 45975 / 45975.xpi / content / clicknlearn.js next >
Text File  |  2009-11-23  |  19KB  |  461 lines

  1. /*
  2.  * Copyright (c) 2009 Bui Viet Thanh (thanhbv@gmail.com).
  3.  *
  4.  * This file is part of clicknlearn.
  5.  *
  6.  * clicknlearn is free software: you can redistribute it and/or modify
  7.  * it under the terms of the GNU General Public License as published by
  8.  * the Free Software Foundation, either version 3 of the License, or
  9.  * (at your option) any later version.
  10.  *
  11.  * clicknlearn is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with clicknlearn.  If not, see <http://www.gnu.org/licenses/>.
  18.  */
  19.  
  20. Components.utils.import("resource://clicknlearn/cnldao.js");
  21.  
  22. function Clicknlearn() {
  23.     window.addEventListener("click", this.eventClick, false);
  24.     window.addEventListener("load", this.eventLoad, false);
  25.     window.addEventListener("contextmenu", this.eventContextMenu, true);
  26.     window.addEventListener("keypress", this.eventKeyPress, false);
  27.     this.modifier = 1;//shiftKey + 2 * ctrlKey + 4 * altKey
  28.     this.maxWidth = 640;
  29.     //static:
  30.     this.BOUNDARY_NODES = ['BODY', 'BR', 'CAPTION', 'CENTER', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'INPUT', 'LI', 'OL', 'P', 'PRE', 'SCRIPT', 'SELECT', 'TD', 'TEXTAREA', 'TR', 'UL'];
  31. }
  32.  
  33. Clicknlearn.prototype.eventLoad = function(event) { CNL.eventLoadImpl(event); }
  34. Clicknlearn.prototype.eventLoadImpl = function(event) {
  35.     this.currentDoc = null;
  36.     this.eventClientX = null;
  37.     this.eventClientY = null;
  38.     //the following statement must place here (not in clicknlearn.xul)
  39.     // because if not => document.getElementById("string-bundle") will == null !
  40.     this.stringsBundle = document.getElementById("string-bundle");
  41.     //Oh! see https://wiki.mozilla.org/Labs/JS_Modules#StringBundle
  42.     //we can initialized stringsBundle by the following statement:
  43. //    this.stringsBundle = new StringBundle("chrome://clicknlearn/locale/string-bundle.properties");
  44.     //Oh! :D https://wiki.mozilla.org/Labs/JS_Modules is still LAB!
  45.     //& be implemented in Weave: resource://weave/ext/[StringBundle | Observers | Preferences].js, 
  46.     this.DICLOOKUP = new DicLookup();
  47.     this.REMIND = new Remind();     
  48.     this.clickIdHash = {"requestWordFromOtherSourcesButton": [DicLookup.prototype.requestWordFromOtherSources, this.DICLOOKUP]
  49.         , "remindButton": [Remind.prototype.remindCurrentWord, this.REMIND]
  50.         , "removeWordButton": [Remind.prototype.removeCurrentWord, this.REMIND]
  51.         , "cnl_settingsButton": [this.showPrefsWindow, this]};
  52.     var appcontent = document.getElementById("appcontent");
  53.     if(appcontent)
  54.       appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
  55. }
  56.  
  57. Clicknlearn.prototype.showPrefsWindow = function(){
  58.     window.open("chrome://clicknlearn/content/options.xul", "optionsWindow", "chrome,centerscreen"); 
  59. }
  60.  
  61. /**
  62.  * do something with the loaded page.
  63.  */
  64. Clicknlearn.prototype.onPageLoad = function(event){
  65.     CNL.currentDoc = event.originalTarget; // doc is document that triggered "onload" event
  66.     CNL.attachCss();
  67.     CNL.REMIND.highlightWords();
  68. }
  69.  
  70. /**
  71.  * To fix issue 3:
  72.  * [linux only] shift + right-click on a word still open the context menu
  73.  */
  74. Clicknlearn.prototype.eventContextMenu = function(event) { CNL.eventContextMenuImpl(event); }
  75. Clicknlearn.prototype.eventContextMenuImpl = function(event) {
  76.     // Return if the click was not on an HTML document.
  77.     if (event.target.ownerDocument != "[object XPCNativeWrapper [object HTMLDocument]]")
  78.         return;
  79.     // Check if the correct mouse button and the correct modifier were pressed. If so, look the word up.
  80.     // & prevent the context menu from opening
  81.     if (event.button == 2 && this.modifierIsPressed(event, this.modifier))
  82.         event.preventDefault();
  83. }
  84.  
  85. /**
  86.  * remove lookup div when user press ESC key
  87.  */
  88. Clicknlearn.prototype.eventKeyPress = function(event) { CNL.eventKeyPressImpl(event); }
  89. Clicknlearn.prototype.eventKeyPressImpl = function(event) {
  90.     if(this.DICLOOKUP.div == null)
  91.         return;
  92. //    alert(event.keyCode + ", " + KeyEvent.DOM_VK_ENTER); ??? 13 & 14
  93.     if(event.keyCode == KeyEvent.DOM_VK_ESCAPE)
  94.         this.DICLOOKUP.clearAll();
  95.     else if(event.keyCode == 13){
  96.         var element = event.target;
  97. //        alert(element);
  98.         if(element.id == 'cnlwordbox' && element.value){
  99.             this.DICLOOKUP.currentWord = element.value;
  100.             this.DICLOOKUP.formatCurrentWord();
  101.             this.DICLOOKUP.reset();
  102.             this.DICLOOKUP.div = this.attachDiv(this.DICLOOKUP.divId);
  103.             this.DICLOOKUP.printLookingUp();
  104.             this.DICLOOKUP.requestWord();
  105.         }
  106.     }
  107. }
  108.  
  109. Clicknlearn.prototype.eventClick = function(event) { CNL.eventClickImpl(event); }
  110. Clicknlearn.prototype.eventClickImpl = function(event) {
  111.     // Return if the click was not on an HTML document.
  112.     if (event.target.ownerDocument != "[object XPCNativeWrapper [object HTMLDocument]]")
  113.         return;
  114.     // Check if the correct mouse button and the correct modifier were pressed. If so, look the word up.
  115.     if (event.button == 2 && this.modifierIsPressed(event, this.modifier))
  116.         this.lookUpWord(event);
  117.  
  118.     // If it's a left-click, hide the lookup div
  119.     if (event.button == 0 && this.DICLOOKUP.div != null) {
  120.         var element = event.target;
  121.         var clickOnElementFunc = this.clickIdHash[element.id];
  122.         if(clickOnElementFunc != undefined){
  123.             clickOnElementFunc[0].apply(clickOnElementFunc[1]);
  124.             return;
  125.         }
  126.         var clickTarget = "";
  127.         while (element != null) {
  128.             if(element.id == this.DICLOOKUP.divId){
  129.                 clickTarget = this.DICLOOKUP.divId;
  130.                 break;
  131.             }
  132.             else
  133.                 element = element.parentNode;
  134.         }
  135.         if (clickTarget != this.DICLOOKUP.divId)
  136.             this.DICLOOKUP.clearAll();
  137.     }
  138. }
  139.  
  140. Clicknlearn.prototype.modifierIsPressed = function(event, modifier) {
  141.     return event.shiftKey + 2 * event.ctrlKey + 4 * event.altKey == modifier;     
  142. }
  143.  
  144. /**
  145.  * get current word, and delegate the work looking up the definition from internet to a DicLookup object
  146.  * @param event click event use to get the word and sentence that the user click on
  147.  */
  148. Clicknlearn.prototype.lookUpWord = function(event) {
  149.     this.getCurrentWordAndSentence(event.rangeParent, event.rangeOffset);
  150.     this.currentDoc = event.target.ownerDocument;
  151.     this.eventClientX = event.clientX;
  152.     this.eventClientY = event.clientY;
  153.     this.DICLOOKUP.reset();
  154.     this.DICLOOKUP.div = this.attachDiv(this.DICLOOKUP.divId);
  155.     if (! this.DICLOOKUP.currentWord){
  156.         this.DICLOOKUP.showDivWithEmptyWord();
  157.     } else {
  158.         this.DICLOOKUP.printLookingUp();
  159.         this.DICLOOKUP.requestWord();
  160.     }
  161. }
  162.  
  163. Clicknlearn.prototype.removeDiv = function(div) {
  164.     if (this.currentDoc != null && div != null && div.parentNode != null)
  165.         div.parentNode.removeChild(div);
  166. }
  167.  
  168. Clicknlearn.prototype.attachDiv = function(divId) {
  169.     this.attachCss();
  170.     var div = this.currentDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
  171.     div.id = divId;
  172.     div.className = "clicknlearn";
  173.     div.style.maxWidth = this.maxWidth + "px";
  174.     this.setDivPosition(div);
  175.     return this.currentDoc.body.appendChild(div);
  176. }
  177.  
  178. Clicknlearn.prototype.attachCss = function() {
  179.     var cssAlreadyInPlace = this.currentDoc.getElementById("clicknlearnCSS");
  180.     if (cssAlreadyInPlace == null) {
  181.         var heads = this.currentDoc.getElementsByTagName("head");
  182.         var link = this.currentDoc.createElement("link");
  183.         link.rel = "stylesheet";
  184.         link.id = "clicknlearnCSS";
  185.         link.type = "text/css";
  186.         link.href = "chrome://Clicknlearn/content/css.css";
  187.         heads[0].appendChild(link);
  188.     }
  189. }
  190.  
  191. Clicknlearn.prototype.setDivPosition = function(div) {
  192.     // Set horizontal position.
  193.     if (this.eventClientX + this.maxWidth + 64 > this.currentDoc.width)
  194.         if (this.currentDoc.width > this.maxWidth)
  195.             div.style.left = this.currentDoc.width - this.maxWidth - 64 + "px";
  196.         else
  197.             div.style.left = "0px";
  198.     else
  199.         div.style.left = this.eventClientX + "px";
  200.  
  201.     // Set vertical position.
  202.     div.style.top = this.eventClientY + this.currentDoc.documentElement.scrollTop + this.currentDoc.body.scrollTop + 10 + "px";
  203. }
  204.  
  205. /**
  206.  * Set [this.DICLOOKUP.currentWord, this.DICLOOKUP.currentRemind] = the [word, sentence] that user click on
  207.  * or ["", xxx] if not found
  208.  * @param parent The DOM node that user click on
  209.  * @param offset pos in the parent node that user click on
  210.  */
  211. Clicknlearn.prototype.getCurrentWordAndSentence = function(parent, offset) {
  212.     // Return the selected text if it was clicked on.
  213.     var selection = document.commandDispatcher.focusedWindow.getSelection();
  214.     this.DICLOOKUP.currentWord = "";
  215.     if (selection != "") {
  216.         var mouseIsOverSelection = ! selection.isCollapsed && selection.getRangeAt(0).isPointInRange(parent, offset);
  217.         if (mouseIsOverSelection){
  218.             this.DICLOOKUP.currentWord = selection.toString();
  219.             this.DICLOOKUP.formatCurrentWord();
  220.         }
  221.     }
  222.     var textNodeLists;
  223.     if(! this.DICLOOKUP.currentWord){
  224.         textNodeLists = this.expandTextNodeLists(parent);
  225.         //TODO haven't been tested
  226.         if(textNodeLists[0].length == 0)
  227.             return;
  228.         this.DICLOOKUP.currentWord  = this.findWord(textNodeLists[0], textNodeLists[1], offset);
  229.         this.DICLOOKUP.formatCurrentWord();
  230.     }
  231.     if(this.DICLOOKUP.currentWord){
  232.         let wordData = CNL_DAO.getWordData(this.DICLOOKUP.currentWord);
  233.         if(wordData && wordData.remind){
  234.             this.DICLOOKUP.currentRemind = wordData.remind;
  235. //            this.DICLOOKUP.currentUrl = wordData.originalUrl;
  236.         }
  237.         else{
  238.             if(! textNodeLists)
  239.                 textNodeLists = this.expandTextNodeLists(parent);
  240.             this.DICLOOKUP.currentRemind = this.findSentence(textNodeLists[0], textNodeLists[1], offset);
  241. //            this.DICLOOKUP.currentUrl = this.currentDoc.location.toString();
  242.         }
  243.         if(wordData == null)
  244.             this.DICLOOKUP.remember = false;
  245.         else
  246.             this.DICLOOKUP.remember = wordData.read == 0;
  247.     }
  248. }
  249.  
  250. /**
  251.  * return [prevTextNodeList, nextTextNodeList]
  252.  */
  253. Clicknlearn.prototype.expandTextNodeLists = function(node){
  254. //    alert("click on: " + node.textContent);
  255.     ////////////go up to a tag that make a new line
  256.     var startNode = node;
  257.     var iterateToEndTag = false;
  258.     var prevTextNodeList = new Array();
  259.     while(this.isNotBoundaryNode(startNode.nodeName)){
  260.         if(startNode.nodeType == Node.TEXT_NODE && startNode.textContent)
  261.             prevTextNodeList[prevTextNodeList.length] = startNode;
  262.         if(!iterateToEndTag){
  263.             if(startNode.previousSibling){
  264.                 startNode = startNode.previousSibling;
  265.                 iterateToEndTag = true;
  266.             }
  267.             else
  268.                 startNode = startNode.parentNode;
  269.         }
  270.         else{
  271.             if(startNode.lastChild)//not TextNode
  272.                 startNode = startNode.lastChild;
  273.             else if(startNode.previousSibling)
  274.                 startNode = startNode.previousSibling;
  275.             else{
  276.                 startNode = startNode.parentNode;
  277.                 iterateToEndTag = false;
  278.             }
  279.         }
  280.     }
  281. //    alert("start break line tag: " + startNode.tagName + iterateToEndTag);
  282.     ////////////go down to a tag that make a new line
  283.     var endNode = node;
  284.     var iterateToStartTag = true;
  285.     var nextTextNodeList = new Array();
  286.     while(this.isNotBoundaryNode(endNode.nodeName)){
  287.         if(endNode.nodeType == Node.TEXT_NODE && endNode.textContent)
  288.             nextTextNodeList[nextTextNodeList.length] = endNode;
  289.         if(iterateToStartTag){
  290.             if(endNode.firstChild)
  291.                 endNode = endNode.firstChild;
  292.             else if(endNode.nextSibling)
  293.                 endNode = endNode.nextSibling;
  294.             else{
  295.                 endNode = endNode.parentNode
  296.                 iterateToStartTag = false;
  297.             }
  298.         }
  299.         else{
  300.             if(endNode.nextSibling){
  301.                 endNode = endNode.nextSibling;
  302.                 iterateToStartTag = true;
  303.             }
  304.             else
  305.                 endNode = endNode.parentNode;
  306.         }
  307.     }
  308. //    alert("End break line tag: " + endNode.tagName + iterateToStartTag);
  309.     return [prevTextNodeList, nextTextNodeList];
  310. }
  311.  
  312. /**
  313.  * @param boundaryDelimiters
  314.  * @param prevTextNodeList find from prevTextNodeList[0][offset] backward
  315.  * @param nextTextNodeList find from nextTextNodeList[0][offset] forward
  316.  * @param offset offset of the position we want to start finding.
  317.  *      It is the offset in prevTextNodeList[0] (== nextTextNodeList[0])
  318.  * @return text, or "" if not found
  319.  */
  320. Clicknlearn.prototype.findText = function(boundaryDelimiters, prevTextNodeList, nextTextNodeList, offset){
  321.     var parent = prevTextNodeList[0];
  322.     var startOffset = -1, endOffset = -1;//startOffset == -1 mean rangeStartIsNotSet
  323.     var foundPos, startNodeIndex, endNodeIndex;
  324.     //prevTextNodeList
  325.     foundPos = this.findCharListPositionInTextNodeList(boundaryDelimiters /*" \t\n"*/, prevTextNodeList, offset, true);
  326.     startNodeIndex = foundPos[0];
  327.     startOffset = foundPos[1];
  328.     if(startOffset == -1){
  329.         startNodeIndex = prevTextNodeList.length - 1;
  330.         startOffset = 0;
  331.     }
  332.     //nextTextNodeList
  333.     foundPos = this.findCharListPositionInTextNodeList(boundaryDelimiters, nextTextNodeList, offset, false);
  334.     endNodeIndex = foundPos[0];
  335.     endOffset = foundPos[1];
  336. //    //debug
  337. //    var log = "[";
  338. //    for(var i=0; i< nextTextNodeList.length; i++)
  339. //        log += nextTextNodeList[i].textContent + "]["
  340. //    log += "\n\n" + endNodeIndex + ", " + endOffset;
  341. //    alert(log);
  342. //    //~debug
  343.     if(endOffset == -1){
  344.         endNodeIndex = nextTextNodeList.length - 1;
  345.         endOffset = nextTextNodeList[endNodeIndex].textContent.length - 1;
  346.     }
  347.     var range = parent.ownerDocument.createRange();
  348.     range.setStart(prevTextNodeList[startNodeIndex], startOffset);
  349.     range.setEnd(nextTextNodeList[endNodeIndex], endOffset  + 1);
  350.     //see http://stackoverflow.com/questions/1586231/strange-behaviour-with-range-tostring
  351.     var sel = content.getSelection(); //use HTML window insteads of XUL window
  352.     var prevSelectedRange;
  353.     //TODO need review. we check because sometimes sel.getRangeAt(0) will raise a exception 
  354.     if(sel.rangeCount > 0){
  355.         prevSelectedRange = sel.getRangeAt(0);
  356.         sel.removeAllRanges();
  357.     }
  358.     sel.addRange(range);
  359.     var ret = sel.toString()
  360.     sel.removeAllRanges();
  361.     if (prevSelectedRange != undefined)
  362.         sel.addRange(prevSelectedRange);
  363.     return ret;
  364. }
  365.  
  366. /**
  367.  * When find sentence, we must care if the pos that user click on (that we start finding) is in a PRE tag
  368.  */
  369. Clicknlearn.prototype.findSentence = function(prevTextNodeList, nextTextNodeList, offset){
  370.     var delimiters = ".?!;|";
  371.     var s = this.isInPreTag(parent)?
  372.         this.findText("\n" + delimiters, prevTextNodeList, nextTextNodeList, offset) :
  373.         this.findText(delimiters, prevTextNodeList, nextTextNodeList, offset);
  374.     return CNLUtils.trim(s, "\n " + delimiters);
  375. }
  376.  
  377. /**
  378.  * When find word, we don't care if the pos that user click on (that we start finding) is in a PRE tag
  379.  */
  380. Clicknlearn.prototype.findWord = function(prevTextNodeList, nextTextNodeList, offset){
  381.     return this.findText(" \t\n./", prevTextNodeList, nextTextNodeList, offset);
  382. }
  383.  
  384. /**
  385.  * return [foundNodeIndex, foundOffset]
  386.  *  or [xxx, -1] if not found
  387.  * @param charList the chatList to find
  388.  * @param textNodeList text node list to find in
  389.  * @param offsetInFirstNode find from pos textNodeList[0][offsetInFirstNode]
  390.  * @param findBackward true if find backward
  391.  */
  392. Clicknlearn.prototype.findCharListPositionInTextNodeList = function(charList, textNodeList, offsetInFirstNode, findBackward){
  393.     var foundNodeIndex = textNodeList.length - 1, offset, i, j, foundOffset;
  394.     if(findBackward){
  395.         foundOffset = -1;
  396.         for(j=0; j<charList.length; j++){
  397.             offset = textNodeList[0].textContent.lastIndexOf(charList[j], offsetInFirstNode);
  398.             if(offset != -1){
  399.                 if(foundNodeIndex > 0 || foundNodeIndex == 0 && foundOffset < offset){
  400.                     foundNodeIndex = 0;
  401.                     foundOffset = offset;
  402.                 }
  403.             }
  404.             else
  405.                 for(i=1; i <= foundNodeIndex; i++){
  406.                     offset = textNodeList[i].textContent.lastIndexOf(charList[j]);
  407.                     if(offset != -1){
  408.                         if(foundNodeIndex > i || foundNodeIndex == i && foundOffset < offset){
  409.                             foundNodeIndex = i;
  410.                             foundOffset = offset;
  411.                             break;
  412.                         }
  413.                     }
  414.                 }
  415.         }
  416.     }
  417.     else{
  418.         foundOffset = 1000;
  419.         for(j=0; j<charList.length; j++){
  420.             offset = textNodeList[0].textContent.indexOf(charList[j], offsetInFirstNode);
  421.             if(offset != -1){
  422.                 if(foundNodeIndex > 0 || foundNodeIndex == 0 && foundOffset > offset){
  423.                     foundNodeIndex = 0;
  424.                     foundOffset = offset;
  425.                 }
  426.             }
  427.             else
  428.                 for(i=1; i <= foundNodeIndex; i++){
  429.                     offset = textNodeList[i].textContent.indexOf(charList[j]);
  430.                     if(offset != -1){
  431. //                        alert("find backward: "+ foundNodeIndex + ", " + i + ", " + foundOffset + ", " + offset);
  432.                         if(foundNodeIndex > i || foundNodeIndex == i && foundOffset > offset){
  433.                             foundNodeIndex = i;
  434.                             foundOffset = offset;
  435.                             break;
  436.                         }
  437.                     }
  438.                 }
  439.         }
  440.         if(foundOffset == 1000)
  441.             foundOffset = -1;
  442.     }
  443.     return [foundNodeIndex, foundOffset];
  444. }
  445.  
  446. Clicknlearn.prototype.isInPreTag = function(node){
  447.     while(node.parentNode){
  448.         node = node.parentNode;
  449.         if(node.tagName == "PRE")
  450.             return true;
  451.     }
  452.     return false;
  453. }
  454.  
  455. /**
  456.  * returns true if the passed node name is not a "boundary" node
  457.  */
  458. Clicknlearn.prototype.isNotBoundaryNode = function(nodeName) {
  459.     return CNLUtils.search(this.BOUNDARY_NODES, nodeName) == -1;
  460. }
  461.